<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    
    <title>String 类真的不可以改变吗？ - Zexun Luo | 罗泽勋</title>
    <meta property="og:title" content="String 类真的不可以改变吗？ - Zexun Luo | 罗泽勋">
    
    <meta name="twitter:card" content="summary">

    
      
    

    
      
      <meta property="description" content="对于 java.lang.String 类，我相信很多人印象里都是它不能被改变。通过查看源码，可以知道 String 有一个 final char 数组的变量，这个变量初始化之后便不能重新赋值。虽然这个变量引用的 char 数组的值虽然可以改变，但是String 类中并没有主动修改 这个 char 数组的方法。这就是通常所说 String 类不可变的依据。
然而在学习反射的时候，我产生了一个疑问： &amp;hellip;">
      <meta property="og:description" content="对于 java.lang.String 类，我相信很多人印象里都是它不能被改变。通过查看源码，可以知道 String 有一个 final char 数组的变量，这个变量初始化之后便不能重新赋值。虽然这个变量引用的 char 数组的值虽然可以改变，但是String 类中并没有主动修改 这个 char 数组的方法。这就是通常所说 String 类不可变的依据。
然而在学习反射的时候，我产生了一个疑问： &amp;hellip;">
      
    

    
    
    
    <meta name="twitter:image" content="/images/logo.png">
    
    

    

    
    


<link href='//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css' rel='stylesheet' type='text/css' />



    <link rel="stylesheet" href="/css/style.css" />
    <link rel="stylesheet" href="/css/fonts.css" />
    
<link rel="stylesheet" href="/css/custom.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer@1.10.0/dist/APlayer.min.css">  

  </head>

  
  <body class="post">
    <div id="pjax-content">
    <header class="masthead">
      

<h1><a href="/"><img src="/images/logo.png" alt="Zexun Luo" /></a></h1>



      <nav class="menu">
  <ul>
  
  
  <li><a href="/">主页</a></li>
  
  <li><a href="/about/">关于</a></li>
  
  <li><a href="/blog/">日志</a></li>
  
  <li><a href="/post/">技术</a></li>
  
  

  </ul>
</nav>

    </header>
    <article class="main">
      <header class="title">
      
<h1>String 类真的不可以改变吗？</h1>


<h3>罗泽勋 / 
2020-12-15</h3>

<hr>


      </header>



<p>对于 java.lang.String 类，我相信很多人印象里都是它不能被改变。通过查看源码，可以知道 String 有一个 final char 数组的变量，这个变量初始化之后便不能重新赋值。虽然这个变量引用的 char 数组的值虽然可以改变，但是String 类中并没有主动修改 这个 char 数组的方法。这就是通常所说 String 类不可变的依据。</p>
<p>然而在学习反射的时候，我产生了一个疑问：我们不可以通过反射修改 String 内部的 char 数组的值吗？答案是可以的，了解 Java 反射的人很容易就做到这点。那为什么还说 String 不可变呢？在思考过后，我得出一个答案：String 的不可变说的是一种约束规范。换句话说，String 的不可变是有好处的。在实际工作中，代码是自己写来用的，没有人会费劲给自己找麻烦，而没有任何益处。</p>
<p>事情到这里就完了？不，最近我再去了解 String 、String Pool（字符串常量池）的时候，又有了一些疑惑，于是亲手操刀给String 来了一次手术。最后也是加深了对 String 的理解，也验证了上面的观点。先上代码（如果你对任一实验结果感到疑惑，没关系，请带着疑惑来看后面的对照实验）：</p>
<pre><code>    //实验1
    String s1 = &quot;1&quot;;
    Field valueField = String.class.getDeclaredField(&quot;value&quot;);
    valueField.setAccessible(true);
    char[] value = (char[]) valueField.get(s1);
    value[0] = '2';
    System.out.println(s1);                 //2
    System.out.println((s1 == &quot;1&quot;));        //true
</code></pre><p>上述实验 1 做的事情就是：用 <code>s1 = &quot;1&quot; </code> 的方式创建字符串，用反射修改它的值为 <code>&quot;2&quot;</code>,最后用它与 <code>&quot;1&quot;</code> 比较，结果是 <code>true</code> 。琢磨琢磨，再看下一段代码：</p>
<pre><code>    //实验2
    String s2 = new String(&quot;1&quot;);
    Field valueField = String.class.getDeclaredField(&quot;value&quot;);
    valueField.setAccessible(true);
    char[] value2 = (char[]) valueField.get(s2);
    value2[0] = '2';
    System.out.println(s2);                //2
    System.out.println((s2 == &quot;1&quot;));       //false
</code></pre><p>上述实验 2 和实验 1 唯一的区别是字符串创建方式，实验2是通过 <code>new String(&quot;1&quot;)</code> 方式创建，实验1是通过 <code>= &quot;1&quot;</code> 的方式创建。而实验2的结果： <code>s2 == &quot;1&quot;</code> 是 <code>false</code> 。</p>
<p>到这里，可以停一会了。很多技术文章中都做过类似上面的实验（通过比较创建方式的区别、反射修改等)，来分析 String 、String Pool 的原理和设计理念。但我总觉得以此说明最终的结论还不够严谨、或者说还不够直观。接下来请看——魔鬼实验：</p>
<pre><code>    //实验3 
    String s1 = &quot;1&quot;;
    Field valueField = String.class.getDeclaredField(&quot;value&quot;);
    valueField.setAccessible(true);
    char[] value = (char[]) valueField.get(s1);
    value[0] = '2';
    System.out.println(s1);                 //2
    System.out.println((s1 == &quot;1&quot;));        //true
    System.out.println(&quot;1&quot;);                //2
</code></pre><p>不难发现，实验3仅仅只是在实验1的基础上输出了 <code>&quot;1&quot;</code> 的值。然而 <code>&quot;1&quot;</code> 输出的值为 2，这样的结果，也许有人会困惑，没关系,看完几个对比实验，相信你会对相关理论（String 、String Pool 等）有了更直观的认识。接下来，请看终极实验：</p>
<pre><code>    //实验4
    String s2 = new String(&quot;1&quot;);
    Field valueField = String.class.getDeclaredField(&quot;value&quot;);
    valueField.setAccessible(true);
    char[] value2 = (char[]) valueField.get(s2);
    value2[0] = '2';
    System.out.println(s2);                 //2
    System.out.println((s2 == &quot;1&quot;));        //false
    System.out.println(&quot;1&quot;);                //2
</code></pre><p>同样的，上述实验 4 只是在实验 2 的基础上输出了 <code>&quot;1&quot;</code> 的值。结果 <code>&quot;1&quot;</code> 的值还是 2。</p>
<p><strong>结论：</strong><br>
基于对 String 类和字符串常量池（String Pool） 的了解，我对以上实验作出解释（详细理论下回贴出）:</p>
<p>实验 3 中 字符串 <code>s1</code> 通过直接赋值字符串常量 <code>&quot;1&quot;</code> 的方式创建，因此 <strong><code>s1</code> 会直接引用字符串常量池中 <code>&quot;1&quot;</code> 对应的对象</strong>。对 <code>s1</code> 的修改，也就是对常量池中 <code>&quot;1&quot;</code> 对象的修改，因此 <code>s1</code> 和 <code>&quot;1&quot;</code> 的值都是修改后的 2，而 <code>s1 == &quot;1&quot;</code> 也是 <code>true</code>,因为它们是同一个对象的引用。</p>
<p>实验 4 中 字符串 <code>s2</code> 通过 <code>new String(&quot;1&quot;)</code>的方式创建，因此，<strong><code>s2</code> 会被新建在堆中，并且，<code>s2</code> 的 <code>char</code> 数组变量会直接引用字符串常量池中 &ldquo;1&quot;对象的 <code>char</code> 数组</strong>，也就是说虽然 <code>s2</code> 是新创建的对象，但是 <code>s2</code> 里存储字符的 <code>char</code> 数组是创建时传进来的字符串常量 <code>&quot;1&quot;</code> 的 <code>char</code> 数组。因此，<code>s2</code> 修改 <code>char</code> 数组,<code>&quot;1&quot;</code> 的也会变。<code>s2</code> 和 <code>&quot;1&quot;</code> 的值都是修改后的 2 。而 <code>s2 == &quot;1&quot;</code> 是 <code>false</code> 的，因为它们引用的不是同一个对象。</p>
<p>最终结论，String 类真的是“不可以”变的！因为这不仅仅是它的特点，更是它的设计初衷。（<del>任何想通过反射修改它的人都应该被拉去祭天</del>)</p>
<p>如果你发现任何问题，请<a href="/about">联系</a>我。</p>


  
  <nav class="post-nav">
    <span class="nav-prev"></span>
    <span class="nav-next"><a href="/post/2020-12-26/">Linux 不同发行版的选择</a> &rarr;</span>
  </nav>
  <script type="text/javascript">
  document.addEventListener('keyup', function(e) {
    if (e.target.nodeName.toUpperCase() != 'BODY') return;
    var url = false;
    if (e.which == 37) {  
      
    } else if (e.which == 39) {  
      
      url = '\/post\/2020-12-26\/';
      
    }
    if (url) window.location = url;
  });
  </script>
  
  </article>
  </div>
  <footer>
  
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="/js/jquery.pjax.js"></script>
<script src="/js/pjax.config.js"></script>

<div class="music">
<div id="player" class="aplayer"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/aplayer@1.10.0/dist/APlayer.min.js"></script>  

<script type="text/javascript">
const ap = new APlayer({
    container: document.getElementById('player'),
    order: 'random',
    listFolded: true,
    listMaxHeight: 90,
    theme:	'#b7daff',
    audio: [
    {
        name: 'きこえるかしら',
        artist: '大和田りつこ',
        url: 'https://cdn.jsdelivr.net/gh/lzxqaq/jsdelivr@master/music/赤毛のアンOP_きこえるかしら_大和田りつこ.mp3',
        cover: 'https://cdn.jsdelivr.net/gh/lzxqaq/jsdelivr@master/image/赤毛のアンOP_きこえるかしら_大和田りつこ.jpeg'
      },
      {
        name: 'まっててごらん',
        artist: '大杉久美子',
        url: 'https://cdn.jsdelivr.net/gh/lzxqaq/jsdelivr@master/music/大杉久美子_ネリー・シュワルツ_まっててごらん.mp3',
        cover: 'https://cdn.jsdelivr.net/gh/lzxqaq/jsdelivr@master/image/大杉久美子_ネリー・シュワルツ_まっててごらん.png'
      },
      {
        name: 'ペリーヌ物語',
        artist: '片头曲和片尾曲',
        url: 'https://cdn.jsdelivr.net/gh/lzxqaq/jsdelivr@master/music/世界名作劇場_ペリーヌ物語_OP_ED.mp3',
        cover: 'https://cdn.jsdelivr.net/gh/lzxqaq/jsdelivr@master/image/世界名作劇場_ペリーヌ物語_OP_ED.jpg'
      },
      {
        name: 'ほほえみの魔法',
        artist: 'トラップ一家物語',
        url: 'https://cdn.jsdelivr.net/gh/lzxqaq/jsdelivr@master/music/Trapp_Family_Story_Hohoemi_no_mahou_ほほえみの魔法_Smil.mp3',
        cover: 'https://cdn.jsdelivr.net/gh/lzxqaq/jsdelivr@master/image/Trapp_Family_Story_Hohoemi_no_mahou_ほほえみの魔法_Smil.jpg'
      },
      {
        name: 'It is a Small world',
        artist: 'Disney Classics',
        url: 'https://cdn.jsdelivr.net/gh/lzxqaq/jsdelivr@master/music/Disney_Classics_It_s_a_Small_World_It_s_a_Small.mp3',
        cover: 'https://cdn.jsdelivr.net/gh/lzxqaq/jsdelivr@master/image/Disney_Classics_It_s_a_Small_World_It_s_a_Small.jpg'
      },
      {
        name: 'Forrest Gump Suite',
        artist: 'Alan Silvestri',
        url: 'https://cdn.jsdelivr.net/gh/lzxqaq/jsdelivr@master/music/Alan_Silvestri_Forrest_Gump_Suite2.mp3',
        cover: 'https://cdn.jsdelivr.net/gh/lzxqaq/jsdelivr@master/image/Alan_Silvestri_Forrest_Gump_Suite.jpeg'
      },





  ]
});
</script>




<script async src="/js/fix-toc.js"></script>

<script async src="/js/center-img.js"></script>

<script async src="/js/right-quote.js"></script>

<script async src="/js/no-highlight.js"></script>

<script async src="/js/fix-footnote.js"></script>

<script async src="/js/math-code.js"></script>

<script async src="/js/external-link.js"></script>

<script async src="/js/alt-title.js"></script>

<script async src="/js/header-link.js"></script>




  



<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>



<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/r.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/yaml.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/tex.min.js"></script>
<script>hljs.configure({languages: []}); hljs.initHighlightingOnLoad();</script>



  
  <hr>
  <div class="copyright">© <a href="/">Zexun Luo</a> 2020 - 2021</div>
  
  </footer>
  
  </body>
</html>

